/* Copyright (c) 2007  Peter Palotas
 *  
 *  This software is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *  
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *  
 *      1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 *  
 *      2. Altered source versions must be plainly marked as such, and must not be
 *      misrepresented as being the original software.
 *  
 *      3. This notice may not be removed or altered from any source
 *      distribution.
 *  
 *  
 *  $Id: ConsoleProcessStartInfo.cs 19 2007-08-15 13:14:32Z palotas $
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Diagnostics;

namespace Plossum.Win32
{
    /// <summary>
    /// Specifies a set of values used when starting a console process using the <see cref="ConsoleProcess"/> class.
    /// </summary>
    public sealed class ConsoleProcessStartInfo
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ConsoleProcessStartInfo"/> class
        /// without specifying a file name with which to start the process. 
        /// </summary>
        /// <remarks>You must set at least the <see cref="FileName"/> property 
        /// before starting the process.</remarks>
        public ConsoleProcessStartInfo()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConsoleProcessStartInfo"/> class and specifies a file name such 
        /// as an application or document with which to start the process. 
        /// </summary>
        /// <param name="fileName">An application or document with which to start a process.</param>
        /// <remarks>You can change the FileName property after calling this constructor, up to the time that the process starts. After you start the process, changing these values has no effect.</remarks>
        public ConsoleProcessStartInfo(string fileName)
        {
            mFileName = fileName;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConsoleProcessStartInfo"/> class
        /// and specifies an application file name with which to start the process, as well as a set of command line 
        /// arguments to pass to the application. 
        /// </summary>
        /// <param name="fileName">An application with which to start a process.</param>
        /// <param name="arguments">Command line arguments to pass to the application when the process starts.</param>
        /// <remarks>You can change the FileName or Arguments properties after calling this constructor, up to the time that the process starts. After you start the process, changing these values has no effect.</remarks>
        public ConsoleProcessStartInfo(string fileName, string arguments)
        {
            mFileName = fileName;
            mArguments = arguments;
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Gets or sets the application or document to start. 
        /// </summary>
        /// <value>The name of the application to start.</value>
        public string FileName
        {
            get { return mFileName; }
            set { mFileName = value; }
        }

        /// <summary>
        /// Gets or sets the set of command line arguments to use when starting the application. 
        /// </summary>
        /// <value>File type-specific arguments that the system can associate with the application specified in the FileName property. The default is an empty string (""). The maximum string length is 2,003 characters in .NET Framework applications and 488 characters in .NET Compact Framework applications.</value>
        public string Arguments 
        {
            get { return mArguments; }
            set { mArguments = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the process handle will be inheritable or not.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instances process handle is inheritable; otherwise, <c>false</c>.
        /// </value>
        public bool IsProcessHandleInheritable
        {
            get { return mIsHandleInheritable; }
            set { mIsHandleInheritable = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the new process will inherit each inheritable handle in the calling process. 
        /// </summary>
        /// <value><c>true</c> if the new process will inherit each inheritable handle; otherwise, <c>false</c>.</value>
        /// <remarks>This property must be set to <c>true</c> if any stream is redirected. Also note that inherited handles 
        /// have the same value and access rights as the original handles.</remarks>
        public bool InheritHandles
        {
            get { return mInheritHandles; }
            set { mInheritHandles = value;  }
        }

        /// <summary>
        /// Gets search paths for files, directories for temporary files, application-specific options, and other similar information.
        /// </summary>
        /// <value> A StringDictionary that provides environment variables that apply to this process and child processes.</value>
        /// <remarks>Although you cannot set the EnvironmentVariables property, you can modify the StringDictionary returned by the property. For example, the following code adds a TempPath environment variable: myProcess.StartInfo.EnvironmentVariables.Add("TempPath", "C:\\Temp").</remarks>
        public StringDictionary EnvironmentVariables
        {
            get { return mEnvironmentVariables;  }
        }

        /// <summary>
        /// Gets or sets the initial directory for the process to be started. 
        /// </summary>
        /// <value>The full path to the current directory for the process. The string can also specify a UNC path. The default is <c>null</c>. </value>
        /// <remarks>If this parameter is <c>null</c>, the new process will have the same current drive and directory as the calling process.</remarks>
        public string WorkingDirectory
        {
            get { return mWorkingDirectory; }
            set { mWorkingDirectory = value; }

        }

        /// <summary>
        /// Gets or sets the preferred encoding for error output. 
        /// </summary>
        /// <value>An Encoding object representing the preferred encoding for error output. The default is a null reference.</value>
        /// <remarks>If the value of the StandardErrorEncoding property is a null reference (Nothing in Visual Basic), the process uses the default standard error encoding for error output. The StandardErrorEncoding property must be set before the process is started. Setting this property does not ensure that the process will use the specified encoding; the process will only use those encodings that it supports. The application should be tested to determine what encodings are supported.</remarks>
        public Encoding StandardErrorEncoding
        {
            get { return mStderrEncoding; }
            set { mStderrEncoding = value; }
        }

        /// <summary>
        /// Gets or sets the preferred encoding for standard output. 
        /// </summary>
        /// <value>An Encoding object representing the preferred encoding for standard output. The default is a null reference.</value>
        /// <remarks>If the value of the StandardOutputEncoding property is a null reference (Nothing in Visual Basic), the process uses the default standard error encoding for error output. The StandardErrorEncoding property must be set before the process is started. Setting this property does not ensure that the process will use the specified encoding; the process will only use those encodings that it supports. The application should be tested to determine what encodings are supported.</remarks>
        public Encoding StandardOutputEncoding
        {
            get { return mStdoutEncoding; }
            set { mStdoutEncoding = value; }
        }

        /// <summary>
        /// Gets or sets the preferred encoding for standard input. 
        /// </summary>
        /// <value>An Encoding object representing the preferred encoding for standard input. The default is a null reference.</value>
        /// <remarks>If the value of the StandardInputEncoding property is a null reference (Nothing in Visual Basic), the process uses the default standard error encoding for error output. The StandardErrorEncoding property must be set before the process is started. Setting this property does not ensure that the process will use the specified encoding; the process will only use those encodings that it supports. The application should be tested to determine what encodings are supported.</remarks>
        public Encoding StandardInputEncoding
        {
            get { return mStdinEncoding; }
            set { mStdinEncoding = value; }
        }

        /// <summary>
        /// Gets or sets the priority class of the new process.
        /// </summary>
        /// <value>The new process's priority class, which is used to determine the scheduling priorities of the process's threads. The default is Normal</value>
        public ProcessPriorityClass PriorityClass
        {            
            get { return mPriorityClass; }
            set { mPriorityClass = value; }
        }

        /// <summary>
        /// Gets or sets the window title of the console window if any is created.
        /// </summary>
        /// <value>The title of the console window if any is created. The default is <c>null</c></value>
        /// <remarks>this is the title displayed in the title bar if a new console window is created. If <c>null</c>, the name of the executable file is used as the window title instead.
        /// This parameter must be <c>null</c> for processes that do not create a new console window.</remarks>
        public string ConsoleWindowTitle
        {
            get { return mConsoleWindowTitle; }
            set { mConsoleWindowTitle = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the error output of an application is written to the <see cref="ConsoleProcess.StandardError"/>
        /// </summary>
        /// <value>
        /// 	<c>true</c> to write error output to ConsoleProcess.StandardError; otherwise, <c>false</c>.
        /// </value>
        public bool RedirectStandardError
        {
            get { return mRedirectStderr; }
            set { mRedirectStderr = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the standard output of an application is written to the <see cref="ConsoleProcess.StandardOutput"/>
        /// </summary>
        /// <value>
        /// 	<c>true</c> to write standard output to ConsoleProcess.StandardOutput; otherwise, <c>false</c>.
        /// </value>
        public bool RedirectStandardOutput
        {
            get { return mRedirectStdout; }
            set { mRedirectStdout = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the input for an application is read from the <see cref="ConsoleProcess.StandardInput"/> stream. 
        /// </summary>
        /// <value>
        /// 	<c>true</c> to read input from ConsoleProcess.StandardInput; otherwise, <c>false</c>.
        /// </value>
        public bool RedirectStandardInput
        {
            get { return mRedirectStdin; }
            set { mRedirectStdin = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the new process does not inherit the error mode of the calling process.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the new process does not inherit the error mode of the calling process; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>
        /// <para>If set to <c>true</c> the new process does not inherit the error mode of the calling process. Instead, the new process gets the default error mode.</para>
        /// <para>This feature is particularly useful for multi-threaded shell applications that run with hard errors disabled.</para>
        /// <para>The default behavior is for the new process to inherit the error mode of the caller. Setting this flag changes that default behavior.</para>
        /// </remarks>
        public bool CreateDefaultErrorMode
        {
            get { return IsPcfBitSet(UnsafeNativeMethods.ProcessCreationFlags.CREATE_DEFAULT_ERROR_MODE); }
            set { SetPcfBit(UnsafeNativeMethods.ProcessCreationFlags.CREATE_DEFAULT_ERROR_MODE, value);  }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the new process has a new console, instead of inheriting its parent's console (the default).
        /// </summary>
        /// <value><c>true</c> if the new process will have a new console; otherwise, <c>false</c>.</value>
        public bool CreateNewConsole
        {
            get { return IsPcfBitSet(UnsafeNativeMethods.ProcessCreationFlags.CREATE_NEW_CONSOLE); }
            set { SetPcfBit(UnsafeNativeMethods.ProcessCreationFlags.CREATE_NEW_CONSOLE, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the new process will be the root process of a new process group..
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the new process will be the root process of a new process group; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>The process group includes all processes that are descendants of this root process. The process 
        /// identifier of the new process group is the same as the process identifier, which is returned 
        /// by <see cref="ConsoleProcess.Id"/> after the process is started. Process groups are used by the 
        /// GenerateConsoleCtrlEvent function to enable sending a CTRL+BREAK signal to a group of console processes.
        /// <para>If this flag is specified, CTRL+C signals will be disabled for all processes within the new process group.</para>
        /// <para>This flag is ignored if specified with <see cref="CreateNewConsole"/>.</para></remarks>
        public bool CreateNewProcessGroup
        {
            get { return IsPcfBitSet(UnsafeNativeMethods.ProcessCreationFlags.CREATE_NEW_PROCESS_GROUP); }
            set { SetPcfBit(UnsafeNativeMethods.ProcessCreationFlags.CREATE_NEW_PROCESS_GROUP, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the process is a console application that is being run without a console window.
        /// </summary>
        /// <value><c>true</c> if the process is a console application that will be run without a console window.; otherwise, <c>false</c>.</value>
        /// <remarks><para>The process is a console application that is being run without a console window. Therefore, the console handle for the application is not set.</para>
        /// <para>This flag is ignored if the application is not a console application, or if it is used with either <see cref="CreateNewConsole"/> or <see cref="DetachedProcess"/>.</para>
        /// </remarks>
        public bool CreateNoWindow
        {
            get { return IsPcfBitSet(UnsafeNativeMethods.ProcessCreationFlags.CREATE_NO_WINDOW); }
            set { SetPcfBit(UnsafeNativeMethods.ProcessCreationFlags.CREATE_NO_WINDOW, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating if the new process does not inherit its parent's console.
        /// </summary>
        /// <value><c>true</c> if the new process should not inherit its parent's console; otherwise, <c>false</c>.</value>
        /// <remarks>This value cannot be used with <see cref="CreateNewConsole"/></remarks>
        public bool DetachedProcess
        {
            get { return IsPcfBitSet(UnsafeNativeMethods.ProcessCreationFlags.DETACHED_PROCESS); }
            set { SetPcfBit(UnsafeNativeMethods.ProcessCreationFlags.DETACHED_PROCESS, value); }
        }
        
        #endregion

        #region Internal properties

        internal UInt32 CreationFlags
        {
            get
            {
                UInt32 cf = (UInt32)mCreationFlags;
                switch (mPriorityClass)
                {
                    case ProcessPriorityClass.AboveNormal:
                        cf |= (UInt32)UnsafeNativeMethods.ProcessCreationFlags.ABOVE_NORMAL_PRIORITY_CLASS;
                        break;
                    case ProcessPriorityClass.BelowNormal:
                        cf |= (UInt32)UnsafeNativeMethods.ProcessCreationFlags.BELOW_NORMAL_PRIORITY_CLASS;
                        break;
                    case ProcessPriorityClass.High:
                        cf |= (UInt32)UnsafeNativeMethods.ProcessCreationFlags.HIGH_PRIORITY_CLASS;
                        break;
                    case ProcessPriorityClass.Idle:
                        cf |= (UInt32)UnsafeNativeMethods.ProcessCreationFlags.IDLE_PRIORITY_CLASS;
                        break;
                    case ProcessPriorityClass.Normal:
                        cf |= (UInt32)UnsafeNativeMethods.ProcessCreationFlags.NORMAL_PRIORITY_CLASS;
                        break;
                    case ProcessPriorityClass.RealTime:
                        cf |= (UInt32)UnsafeNativeMethods.ProcessCreationFlags.REALTIME_PRIORITY_CLASS;
                        break;
                }
                return cf;
            }
        }
        
        #endregion

        #region Private methods

        /// <summary>
        /// Modifies mProcessCreationFlags by setting or clearing the bits represented by the specified bitmask.
        /// </summary>
        /// <param name="bitMask">The bit mask.</param>
        /// <param name="set">If set to <c>true</c> the bits set in <see cref="mCreationFlags"/> will be set, otherwise 
        /// those bits will be cleared.</param>
        private void SetPcfBit(UnsafeNativeMethods.ProcessCreationFlags bitMask, bool set)
        {
            if (set)
                mCreationFlags |= (int)bitMask;
            else
                mCreationFlags &= ~(int)bitMask;
        }


        /// <summary>
        /// Determines whether the bit(s) set in <paramref name="bitMask"/> are set in <see cref="mCreationFlags"/>.
        /// </summary>
        /// <param name="bitMask">The bit mask.</param>
        /// <returns>
        /// 	<c>true</c> if the bit(s) set in <paramref name="bitMask"/> are all set in <see cref="mCreationFlags"/>; otherwise, <c>false</c>.
        /// </returns>
        private bool IsPcfBitSet(UnsafeNativeMethods.ProcessCreationFlags bitMask)
        {
            return (mCreationFlags & (int)bitMask) == (int)bitMask;
        }

        #endregion

        #region Private fields

        private string mFileName;
        private string mArguments;
        private bool mIsHandleInheritable;
        private bool mInheritHandles;
        private StringDictionary mEnvironmentVariables = new StringDictionary();
        private string mWorkingDirectory;
        private Encoding mStderrEncoding;
        private Encoding mStdoutEncoding;
        private Encoding mStdinEncoding;

        private Int32 mCreationFlags;
        private ProcessPriorityClass mPriorityClass = ProcessPriorityClass.Normal;
        private string mConsoleWindowTitle;
        private bool mRedirectStdout;
        private bool mRedirectStderr;
        private bool mRedirectStdin;
        #endregion
    }
}
